import { CSLine } from '@amcax/base';
import {
  LabelWithIcon,
  LabelWithIconProps,
} from '@app-cad/common/utils/CSS2DLabelWithIcon/LabelWithIcon';
import { ctx2 } from '@app-cad/module';
import { Object3D, Vector3, Matrix4 } from 'three-legacy';
import { CSS2DReactObject } from '../../components/css-2d-react-object/CSS2DReactObject';
import { calculateDeltaMatrix } from '../camera';

interface CSS2DLabelProps
  extends Omit<LabelWithIconProps, 'onDragStart' | 'onDrag'> {
  centers: [Vector3, Vector3];
}

export class CSS2DLabelWithIcon {
  private readonly centers: [Vector3, Vector3];
  private readonly position: Vector3;
  private readonly object: CSS2DReactObject<LabelWithIconProps>;
  private readonly lines: [CSLine, CSLine];

  private parent: Object3D | null = null;

  private dragStartScreen: { x: number; y: number } | null = null;
  private dragStartPosition: Vector3 | null = null;

  constructor(props: CSS2DLabelProps) {
    this.centers = props.centers;

    const div = document.createElement('div');
    div.style.cursor = 'move';
    this.position = this.getInitialPosition();
    const { x, y, z } = this.position;

    this.object = new CSS2DReactObject<LabelWithIconProps>(div, LabelWithIcon, {
      ...props,
      onDrag: this.handleDrag,
      onDragStart: this.handleDragStart,
    });

    this.object.position.set(x, y, z);

    this.lines = this.centers.map(
      (vec) =>
        new CSLine({
          vertexs: [vec, this.position],
          lineWidth: 1,
          color: '#0000ff',
        }),
    ) as [CSLine, CSLine];
  }

  private getInitialPosition() {
    const [A, B] = this.centers;
    const camera = ctx2.viewer.sceneSetup.camera;
    const cameraPos = camera.position;
    const cameraDir = new Vector3();
    camera.getWorldDirection(cameraDir);

    const position = new Vector3().addVectors(A, B).multiplyScalar(0.5);
    const toCamera = new Vector3().subVectors(cameraPos, position);
    const centersDistance = A.distanceTo(B);
    const toCameraVec = toCamera.clone().setLength(centersDistance * 0.5);
    const upVec = camera.up.clone().setLength(centersDistance * 0.25);

    return position.add(toCameraVec).add(upVec);
  }

  private handleDragStart = (x, y) => {
    this.dragStartScreen = { x, y };
    this.dragStartPosition = this.position.clone();
  };

  private handleDrag = (x: number, y: number) => {
    if (!this.dragStartScreen || !this.dragStartPosition) {
      return;
    }

    const currentScreen = { x, y };
    const originalMatrix = new Matrix4().makeTranslation(
      this.dragStartPosition.x,
      this.dragStartPosition.y,
      this.dragStartPosition.z,
    );
    const deltaMatrix = calculateDeltaMatrix(
      this.dragStartScreen,
      currentScreen,
      originalMatrix,
    );

    const newPos = this.dragStartPosition
      .clone()
      .add(
        new Vector3(
          deltaMatrix.elements[12],
          deltaMatrix.elements[13],
          deltaMatrix.elements[14],
        ),
      );

    this.position.copy(newPos);
    this.object.position.set(newPos.x, newPos.y, newPos.z);
    this.lines.forEach((line, i) => {
      line.setVertexs([this.centers[i], this.position]);
    });

    ctx2.viewer.requestRender();
  };

  addTo(parent: Object3D) {
    this.parent = parent;
    parent.add(this.object);
    this.lines.forEach((line) => parent.add(line));
  }

  destroy() {
    this.parent?.remove(this.object);
    this.lines?.forEach((line) => this.parent?.remove(line));

    this.object?.destroy();
    this.lines?.forEach((line) => line.destroy());
  }
}
